using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace RapidHDL.Fabric
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using RapidHDL;

    public class XMLCore : FabricComponent
    {
        RapidHDL.Fabric.MicroCoreBlock oMicroCore;
        RapidHDL.Fabric.ProgramROMRAMBlock oProgram;
        MicroCoreTable oCoreTable;
        XmlDocument oXML;
        XmlNode oCoreNode = null;
        List<string> lsSignalViews = new List<string>();
       

        public XMLCore(Component poParentComponent, string psName, ClockComponent poClock, NodeVector pnvReset, string psXMLPath)
            : base(poParentComponent, psName, poClock, pnvReset,false)
        {
            oXML = new XmlDocument();
            oXML.Load("c:\\src\\rapidhdl\\extras\\fabric.xml");
            BaseDefineFabric();
        }


        public override void DefineFabric()
        {

            if (oXML.DocumentElement.Name != "RapidHardware")
                throw new Exception("XML document root is not RapidHardware");
            foreach (XmlNode oNode in oXML.DocumentElement.ChildNodes)
            {
                switch (oNode.Name)
                {
                    case "ExternalBusInput":
                        AddExternalBusInput(oNode);
                        break;
                    case "ExternalBusOutput":
                        AddExternalBusOutput(oNode);
                        break;
                    case "Register":
                        AddRegister(oNode);
                        break;
                    case "ALU":
                        AddALU(oNode);
                        break;
                    case "LogicBlock":
                        AddLogicBlock(oNode);
                        break;
                    case "Join":
                        AddJoin(oNode);
                        break;
                    case "ProgramROMRAM":
                        AddProgramROMRAM(oNode);
                        break;
                    /*case "MicroCore": 
                        AddMicroCore(oNode);
                        break;*/
                    case "Constant":
                        AddConstant(oNode);
                        break;
                    case "BitCombine":
                        AddBitCombine(oNode);
                        break;
                    case "BitBreakout":
                        AddBitBreakout(oNode);
                        break;
                    case "RAM":
                        AddRAM(oNode);
                        break;
                    case "StatusOutput":
                        AddStatusOutput(oNode);
                        break;
                    case "Resize":
                        AddResize(oNode);
                        break;
                    case "#comment":
                        break;
                    default:
                        throw new Exception("Unknown XML element '" + oNode.Name + "'");
                }
            }
            if (oCoreNode != null)
                AddControlStates(oCoreNode);
        }

        public void AddExternalBusInput(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iWidth = GetAttributeInt(poNode, "Width");
            ContainerBlock.CreateExternalBusInput(sName, iWidth);
            lsSignalViews.Add(sName + ".READY");
        }

        public void AddExternalBusOutput(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iWidth = GetAttributeInt(poNode, "Width");
            ContainerBlock.CreateExternalBusOutput(sName, iWidth);
        }

        public void AddRegister(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iWidth = GetAttributeInt(poNode, "Width");
            ContainerBlock.CreateRegister(sName, iWidth , true);
            lsSignalViews.Add(sName + ".OUT");
        }

        public void AddALU(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iWidth = GetAttributeInt(poNode, "Width");
            int iShiftWidth = GetAttributeInt(poNode, "ShiftWidth", 1);
            string sALUType = GetAttributeString(poNode, "Type");
            ALUType eType;
            switch (sALUType.ToLower())
            {
                case "add":
                    eType = ALUType.Add;
                    break;
                case "addsub":
                    eType = ALUType.AddSub;
                    break;
                case "andmask":
                    eType = ALUType.AndMask;
                    break;
                case "andreduction":
                    eType = ALUType.AndReduction;
                    break;
                case "compareequal":
                    eType = ALUType.CompareEqual;
                    break;
                case "comparegreater":
                    eType = ALUType.CompareGreater;
                    break;
                case "dec":
                    eType = ALUType.Dec;
                    break;
                case "inc":
                    eType = ALUType.Inc;
                    break;
                case "inv":
                    eType = ALUType.Inv;
                    break;
                case "merge":
                    eType = ALUType.Merge;
                    break;
                case "ormask":
                    eType = ALUType.OrMask;
                    break;
                case "orreduction":
                    eType = ALUType.OrReduction;
                    break;
                case "shiftleftarith":
                    eType = ALUType.ShiftLeftArith;
                    break;
                case "shiftleftlogical":
                    eType = ALUType.ShiftLeftLogical;
                    break;
                case "shiftleftrotate":
                    eType = ALUType.ShiftLeftRotate;
                    break;
                case "shiftrightarith":
                    eType = ALUType.ShiftRightArith;
                    break;
                case "shiftrightlogical":
                    eType = ALUType.ShiftRightLogical;
                    break;
                case "shiftrightrotate":
                    eType = ALUType.ShiftRightRotate;
                    break;
                case "subtract":
                    eType = ALUType.Subtract;
                    break;
                case "twocomplement":
                    eType = ALUType.TwoComplement;
                    break;
                case "xormask":
                    eType = ALUType.XorMask;
                    break;
                default:
                    throw new Exception("ALU Type '" + sALUType + "' is not recognized.");
            }
            ContainerBlock.CreateALU(sName,eType, iWidth,iShiftWidth);
        }
       
        public void AddLogicBlock(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            string sExpression = GetAttributeString(poNode, "Expression");
            sExpression = sExpression.Replace('{', '<').Replace('}', '>');
            ContainerBlock.CreateLogicBlock(sName, sExpression);
        }

        public void AddJoin(XmlNode poNode)
        {
            string [] sFrom = GetAttributeString(poNode, "From").Split('.');
            string[] sTo = GetAttributeString(poNode, "To").Split('.');
            // do better error reporting here if improperly formatted...
            ContainerBlock.Join(sFrom[0], sFrom[1], sTo[0], sTo[1]);
        }
        
        public void AddStatusOutput(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            string[] sFrom = GetAttributeString(poNode, "From").Split('.');
            ContainerBlock.CreateStatusOutput(sName, sFrom[0], sFrom[1]);
        }
        
        public void AddRAM(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iWidth = GetAttributeInt(poNode, "Width");
            int iDepth = GetAttributeInt(poNode, "Depth");
            ContainerBlock.CreateRAM(sName, iWidth, iDepth, true);
        }

        public void AddProgramROMRAM(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iConstantWidth = GetAttributeInt(poNode, "ConstantWidth");
            int iRamOffset = GetAttributeInt(poNode, "RAMOffset");
            int iRamDepth = GetAttributeInt(poNode, "RAMDepth");
            string sROMProgramPath = GetAttributeString(poNode, "ROMProgramPath");
            string sRAMProgramPath = GetAttributeString(poNode, "RAMProgramPath");
            string sConstantRegister = GetAttributeString(poNode, "ConstantRegister");
            string sProgramCounter = GetAttributeString(poNode, "ProgramCounter");

            oCoreNode = GetChildNode(poNode, "MicroCore");
            if (oCoreNode == null)
            {
                throw new Exception("MicroCore definition missing in ProgramROMRAM");
            }
 
            string sCore = GetAttributeString(oCoreNode, "Name");            
            AddOpCodes(oCoreNode);
            oProgram = ContainerBlock.CreateProgramROMRAM(sName, oCoreTable, sROMProgramPath, sRAMProgramPath, iRamOffset, iRamDepth,iConstantWidth);

            int iPCWidth = oProgram.Inputs["PC"].Width;
            int iConstWidth = oProgram.Outputs["CONST"].Width;
            //iIRWidth = oProgram.Outputs["IR"].Width;

            ContainerBlock.CreateRegister(sConstantRegister, iConstantWidth, true);
            ContainerBlock.CreateRegister(sProgramCounter, iPCWidth, true);

            lsSignalViews.Add(sConstantRegister + ".OUT");
            lsSignalViews.Add(sProgramCounter + ".OUT");

            ContainerBlock.CreateALU(sProgramCounter + "_INC", ALUType.Inc, iPCWidth);

            ContainerBlock.Join(sName, "CONST", sConstantRegister , "IN");
            ContainerBlock.Join(sProgramCounter, "OUT", sProgramCounter + "_INC", "IN1");
            ContainerBlock.Join(sName, "IR", sCore, "IR");
            ContainerBlock.Join(sProgramCounter , "OUT", sName, "PC");

            lsSignalViews.Add(sName + ".IR");
        }

        /// <summary>
        /// Add op codes only
        /// </summary>
        /// <param name="poNode"></param>
        private void AddOpCodes(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            oCoreTable = new MicroCoreTable();
            
            foreach (XmlNode oNode in poNode.ChildNodes)
            {
                switch(oNode.Name)
                {
                    case "ControlState":
                        AddControlStateOpCode(oNode);
                        break;
                    case "OpCode":
                        AddOpCode(oNode);
                        break;
                    case "#comment":
                        break;
                    default:
                        throw new Exception("Invalid MicroProm Element '" + oNode.Name + "'");
                }
            }
            oMicroCore = ContainerBlock.CreateMicroCore(sName, oCoreTable);

        }

        /// <summary>
        /// Add control states
        /// </summary>
        /// <param name="poNode"></param>
        private void AddControlStates(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            foreach (XmlNode oNode in poNode.ChildNodes)
            {
                switch (oNode.Name)
                {
                    case "ControlState":
                        AddControlState(oNode);
                        break;
                    case "OpCode":
                        break;
                    case "#comment":
                        break;
                    default:
                        throw new Exception("Invalid MicroProm Element '" + oNode.Name + "'");
                }
            }
        }


        public void AddControlStateOpCode(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            bool bOpCode = GetAttributeBool(poNode, "OpCode");

            if (bOpCode)
            {
                MicroCoreOp oOp = oCoreTable.AddOp(sName);
                oOp.AddStateInstruction(sName);
            }
        }

        public void AddControlState(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            bool bOpCode = GetAttributeBool(poNode, "OpCode");
            ControlState oControlState = ContainerBlock.CreateControlState(sName);
            foreach (XmlNode oNode in poNode.ChildNodes)
            {
                switch(oNode.Name)
                {
                    case "Join":
                        string[] sFrom = GetAttributeString(oNode, "From").Split('.');
                        string[] sTo = GetAttributeString(oNode, "To").Split('.');
                        // do better error reporting here if improperly formatted...
                        oControlState.Join(sFrom[0], sFrom[1], sTo[0], sTo[1]);
                        break;
                    case "Activate":
                        string[] sSignal = GetAttributeString(oNode, "Signal").Split('.');
                        oControlState.Activate(sSignal[0], sSignal[1]);
                        break;
                    case "#comment":
                        break;
                    default:
                        throw new Exception("Unknown Control State Value '" + oNode.Name + "'");
                }
            }
        }


        public void AddOpCode(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            MicroCoreOp oOp = oCoreTable.AddOp(sName);
            foreach (XmlNode oNode in poNode.ChildNodes)
            {
                switch(oNode.Name)
                {
                    case "State":
                        string sState = GetAttributeString(oNode,"ControlState");
                        string sLabel = GetAttributeString(oNode, "Label", "");
                        if (sLabel.Trim().Length > 1)
                            oOp.AddStateInstruction(sState,sLabel);
                        else
                            oOp.AddStateInstruction(sState);
                        break;
                    case "Branch":
                        string sStatus = GetAttributeString(oNode,"Status");
                        string sToLabel = GetAttributeString(oNode, "ToLabel");
                        oOp.AddBranchInstruction(sStatus, sToLabel);
                        break;
                    case "Jump":
                        string sJumpToLabel = GetAttributeString(oNode, "ToLabel");
                        oOp.AddJumpInstruction(sJumpToLabel);
                        break;
                    case "#comment":
                        break;
                    default:                
                        throw new Exception("Unknown OpCode Value '" + oNode.Name + "'");                    
                }
            }
        }

        public void AddConstant(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iValue = GetAttributeInt(poNode, "Value");
            int iWidth = GetAttributeInt(poNode, "Width");
            ContainerBlock.CreateConstant(sName, iValue, iWidth);
        }


        public void AddBitCombine(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            string[] sWidths = GetAttributeString(poNode, "InputWidths").Split(',');
            int[] iWidths = new int[sWidths.Length];
            for (int iIdx = 0; iIdx < sWidths.Length; iIdx++)
                iWidths[iIdx] = Convert.ToInt32(sWidths[iIdx]);
            ContainerBlock.CreateCombine(sName,iWidths);
        }

        public void AddBitBreakout(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            string [] sWidths = GetAttributeString(poNode, "OutputWidths").Split(',');
            int[] iWidths = new int[sWidths.Length];
            for (int iIdx = 0; iIdx < sWidths.Length; iIdx++)
                iWidths[iIdx] = Convert.ToInt32(sWidths[iIdx]);
            ContainerBlock.CreateBreakout(sName, iWidths);
        }

        public void AddResize(XmlNode poNode)
        {
            string sName = GetAttributeString(poNode, "Name");
            int iInputWidth = GetAttributeInt(poNode, "InputWidth");
            int iOutputWidth = GetAttributeInt(poNode, "OutputWidth");
            ContainerBlock.CreateResize(sName, iInputWidth, iOutputWidth);
        }

        public bool GetAttributeBool(XmlNode poNode, string psName)
        {
            string sValue = "false";
            try
            {
                sValue = poNode.Attributes.GetNamedItem(psName).InnerText;
            }
            catch
            {
                sValue = "false";
            }
            return (sValue.ToUpper() == "TRUE");
        }


        public string GetAttributeString(XmlNode poNode, string psName, string psDefault)
        {
            try
            {
                return poNode.Attributes.GetNamedItem(psName).InnerText;
            }
            catch
            {
                return psDefault;
            }
        }

        public string GetAttributeString(XmlNode poNode, string psName)
        {
            return poNode.Attributes.GetNamedItem(psName).InnerText;
        }

        public int GetAttributeInt(XmlNode poNode, string psName, int piDefault)
        {
            try
            {
                return System.Convert.ToInt32(GetAttributeString(poNode, psName, "NAN"));
            }
            catch
            {
                return piDefault;
            }
        }

        public int GetAttributeInt(XmlNode poNode, string psName)
        {
            string sValue = GetAttributeString(poNode,psName);
            return System.Convert.ToInt32(sValue);
        }

        public XmlNode GetChildNode(XmlNode poNode, string psName)
        {
            foreach (XmlNode oNode in poNode.ChildNodes)
            {
                if (oNode.Name == psName)
                    return oNode;
            }
            return null;
        }

        public virtual void AddSignalViews()
        {
            foreach (string sSignalName in lsSignalViews)
            {
                string[] sSignal = sSignalName.Split('.');
                ContainerBlock.AddSignalView(sSignal[0], sSignal[1], SignalFormat.Unsigned);
            }

            oMicroCore.AddSignalViews();
            oProgram.AddSignalViews();
        }

        public void SignalDisplay(HardwareScript poScript, int piClockTime)
        {
            ContainerBlock.SignalDisplay(poScript, piClockTime);
            oMicroCore.SignalDisplay(poScript, piClockTime);
            oProgram.SignalDisplay(poScript, piClockTime);
        }

        public string GetProgramText()
        {
            return oProgram.GetProgramText();
        }

        public Dictionary<int, int> GetProgramAddressToLine()
        {
            return oProgram.GetProgramAddressToLine();
        }

        public void LoadRAMProgram()
        {
            oProgram.LoadRAMProgram();
        }

        public List<string> GetOpCodes()
        {
            List<string> lResult = new List<string>();
            foreach (string sOpCode in oCoreTable.Ops.Keys)
                lResult.Add(sOpCode);

            return lResult;
        }

        public List<int> GetBreakpoints()
        {
            return oProgram.GetBreakpoints();
        }

    }

}
